#include "instruction.h++"

std::map<std::string, Tryte_t> Instruction::symbols = std::map<std::string, Tryte_t>();

Instruction::Instruction(const std::string& filename, const size_t& lineNo, const Tryte_t& address, const std::vector<std::string>& tokens)
	: filename_(filename), lineNo_(lineNo), address_(address), tokens_(tokens) {
	
}

std::string Instruction::getLocation() const {
	return (filename_ + ":" + intToDecString(lineNo_)); 
}

std::vector<std::string> Instruction::getTokens() const {
	return tokens_;
}

Tryte_t Instruction::getSize() const {
	if (tokens_.size() == 0) {
		return 0;
	}
	if (tokens_.at(0).compare("halt") == 0) {
		return 1;
	} else if (tokens_.at(0).compare("nop") == 0) {
		return 1;
	} else if (tokens_.at(0).compare("set") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("read") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("write") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("not") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("and") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("or") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("xor") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("shl") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("shr") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("shlc") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("shrc") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("inc") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("inc3") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("dec") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("dec3") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("add") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("sub") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("cmp") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("addc") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("subc") == 0) {
		return 3;
	} else if (tokens_.at(0).compare("son") == 0) {
		return 1;
	} else if (tokens_.at(0).compare("soz") == 0) {
		return 1;
	} else if (tokens_.at(0).compare("sop") == 0) {
		return 1;
	} else if (tokens_.at(0).compare("ssn") == 0) {
		return 1;
	} else if (tokens_.at(0).compare("ssz") == 0) {
		return 1;
	} else if (tokens_.at(0).compare("ssp") == 0) {
		return 1;
	} else if (tokens_.at(0).compare("jmp") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jon") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("joz") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jop") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jonn") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jonz") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jonp") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jsn") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jsz") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jsp") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jsnn") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jsnz") == 0) {
		return 2;
	} else if (tokens_.at(0).compare("jsnp") == 0) {
		return 2;
	} else {
		return 0;
	}
}

std::string Instruction::assemble() const {
	if (tokens_.size() == 0) {
		return "";
	}
	if (tokens_.size() == 0) {
		// ERROR
		exit(1);
	}
	if (tokens_.size() != getSize()) {
		// ERROR
		exit(1);
	}
	std::string output;
	if (tokens_.at(0).compare("halt") == 0) {
		output += assembleSig1(OP_HALT);
	} else if (tokens_.at(0).compare("nop") == 0) {
		output += assembleSig1(OP_NOP);
	} else if (tokens_.at(0).compare("set") == 0) {
		output += assembleSig2(OP_SET_I);
	} else if (tokens_.at(0).compare("read") == 0) {
		output += assembleSig2(OP_READ_I);
	} else if (tokens_.at(0).compare("write") == 0) {
		output += assembleSig2(OP_WRITE_I);
	} else if (tokens_.at(0).compare("not") == 0) {
		output += assembleSigR(OP_NOT);
	} else if (tokens_.at(0).compare("and") == 0) {
		output += assembleSig2(OP_AND_I);
	} else if (tokens_.at(0).compare("or") == 0) {
		output += assembleSig2(OP_OR_I);
	} else if (tokens_.at(0).compare("xor") == 0) {
		output += assembleSig2(OP_XOR_I);
	} else if (tokens_.at(0).compare("shl") == 0) {
		output += assembleSig2(OP_SHL_I);
	} else if (tokens_.at(0).compare("shr") == 0) {
		output += assembleSig2(OP_SHR_I);
	} else if (tokens_.at(0).compare("shlc") == 0) {
		output += assembleSig2(OP_SHLC_I);
	} else if (tokens_.at(0).compare("shrc") == 0) {
		output += assembleSig2(OP_SHRC_I);
	} else if (tokens_.at(0).compare("inc") == 0) {
		output += assembleSigR(OP_INC);
	} else if (tokens_.at(0).compare("inc3") == 0) {
		output += assembleSigR(OP_INC3);
	} else if (tokens_.at(0).compare("dec") == 0) {
		output += assembleSigR(OP_DEC);
	} else if (tokens_.at(0).compare("dec3") == 0) {
		output += assembleSigR(OP_DEC3);
	} else if (tokens_.at(0).compare("add") == 0) {
		output += assembleSig2(OP_ADD_I);
	} else if (tokens_.at(0).compare("sub") == 0) {
		output += assembleSig2(OP_SUB_I);
	} else if (tokens_.at(0).compare("cmp") == 0) {
		output += assembleSig2(OP_CMP_I);
	} else if (tokens_.at(0).compare("addc") == 0) {
		output += assembleSig2(OP_ADDC_I);
	} else if (tokens_.at(0).compare("subc") == 0) {
		output += assembleSig2(OP_SUBC_I);
	} else if (tokens_.at(0).compare("son") == 0) {
		output += assembleSig1(OP_SON);
	} else if (tokens_.at(0).compare("soz") == 0) {
		output += assembleSig1(OP_SOZ);
	} else if (tokens_.at(0).compare("sop") == 0) {
		output += assembleSig1(OP_SOP);
	} else if (tokens_.at(0).compare("ssn") == 0) {
		output += assembleSig1(OP_SSN);
	} else if (tokens_.at(0).compare("ssz") == 0) {
		output += assembleSig1(OP_SSZ);
	} else if (tokens_.at(0).compare("ssp") == 0) {
		output += assembleSig1(OP_SSP);
	} else if (tokens_.at(0).compare("jmp") == 0) {
		output += assembleSigI(OP_JMP);
	} else if (tokens_.at(0).compare("jon") == 0) {
		output += assembleSigI(OP_JON);
	} else if (tokens_.at(0).compare("joz") == 0) {
		output += assembleSigI(OP_JOZ);
	} else if (tokens_.at(0).compare("jop") == 0) {
		output += assembleSigI(OP_JOP);
	} else if (tokens_.at(0).compare("jonn") == 0) {
		output += assembleSigI(OP_JONN);
	} else if (tokens_.at(0).compare("jonz") == 0) {
		output += assembleSigI(OP_JONZ);
	} else if (tokens_.at(0).compare("jonp") == 0) {
		output += assembleSigI(OP_JONP);
	} else if (tokens_.at(0).compare("jsn") == 0) {
		output += assembleSigI(OP_JSN);
	} else if (tokens_.at(0).compare("jsz") == 0) {
		output += assembleSigI(OP_JSZ);
	} else if (tokens_.at(0).compare("jsp") == 0) {
		output += assembleSigI(OP_JSP);
	} else if (tokens_.at(0).compare("jsnn") == 0) {
		output += assembleSigI(OP_JSNN);
	} else if (tokens_.at(0).compare("jsnz") == 0) {
		output += assembleSigI(OP_JSNZ);
	} else if (tokens_.at(0).compare("jsnp") == 0) {
		output += assembleSigI(OP_JSNP);
	}
	return output;
}

std::string Instruction::dump() const {
	std::string dump = (getLocation() + " [" + intToDecString(address_) + "]");
	for (std::vector<std::string>::const_iterator i = tokens_.begin(); i != tokens_.end(); ++i) {
		dump += (" " + *i);
	}
	return dump;
}

void Instruction::addSymbol(const std::string& string, const Tryte_t& address) {
	symbols.insert(make_pair(string, address));
}

std::string Instruction::dumpSymbols() {
	std::string dump;
	for (std::map<std::string, Tryte_t>::iterator i = symbols.begin(); i != symbols.end(); ++i) {
		dump += (i->first + ": " + intToDecString(i->second) + "\n");
	}
	return dump;
}

std::string Instruction::assembleSig1(const int& op) const {
	return (intToTerString(op) + "\n");
}

std::string Instruction::assembleSigI(const int& op) const {
	std::string output;
	std::map<std::string, Tryte_t>::iterator symbol;
	output += (intToTerString(op) + "\n");
	if (isDecString(tokens_.at(1)) == true) {
		output += (intToTerString(decStringToInt(tokens_.at(1))) + "\n");
	} else if ((symbol = symbols.find(tokens_.at(1))) != symbols.end()) {
		output += (intToTerString(symbol->second - address_ - 2) + "\n");
	} else {
		// ERROR
		exit(1);
	}
	return output;
}

std::string Instruction::assembleSigR(const int& op) const {
	std::string output;
	output += (intToTerString(op) + "\n");
	if (getReg(tokens_.at(1)) == -1) {
		// ERROR
		exit(1);
	}
	output += (intToTerString(getReg(tokens_.at(1))) + "\n");
	return output;
}

std::string Instruction::assembleSig2(const int& op) const {
	std::string output;
	std::map<std::string, Tryte_t>::iterator symbol;
	if (getReg(tokens_.at(2)) == -1) {
		output += (intToTerString(op) + "\n");
		if (getReg(tokens_.at(1)) == -1) {
			// ERROR
			exit(1);
		}
		output += (intToTerString(getReg(tokens_.at(1))) + "\n");
		if (isDecString(tokens_.at(2)) == true) {
			output += (intToTerString(decStringToInt(tokens_.at(2))) + "\n");
		} else if ((symbol = symbols.find(tokens_.at(2))) != symbols.end()) {
			output += (intToTerString(symbol->second - address_ - 2) + "\n");
		} else {
			// ERROR
			exit(1);
		}
	} else {
		output += (intToTerString(op + 1) + "\n");
		if (getReg(tokens_.at(1)) == -1) {
			// ERROR
			exit(1);
		}
		output += (intToTerString(getReg(tokens_.at(1))) + "\n");
		output += (intToTerString(getReg(tokens_.at(2))) + "\n");
	}
	return output;
}

Reg_t getReg(const std::string& regName) {
	for (int i = 0; i < N_REGS; ++i) {
		if (regName.compare(regNames[i]) == 0) {
			return i;
		}
	}
	return -1;
}

bool isDecString(const std::string& string) {
	if (string.find_last_not_of("0123456789") == std::string::npos) {
		return true;
	}
	if (string.find_last_not_of("0123456789") == 0 && string.at(0) == '-') {
		return true;
	}
	return false;
}

Tryte_t decStringToInt(const std::string& decString) {
	Tryte_t integer = 0;
	for (size_t i = decString.find_first_of("0123456789"); i < decString.size(); ++i) {
		integer *= 10;
		integer += (decString.at(i) - '0');
	}
	if (decString.at(0) == '-') {
		integer *= -1;
	}
	return integer;
}

std::string intToDecString(const Tryte_t& integer) {
	Tryte_t temp;
	if (integer < 0) {
		temp = -1 * integer;
	} else if (integer > 0) {
		temp = integer;
	} else {
		return "0";
	}
	std::string decString;
	while (temp != 0) {
		decString.insert(decString.begin(), "0123456789"[temp % 10]);
		temp /= 10;
	}
	if (integer < 0) {
		decString.insert(decString.begin(), '-');
	}
	return decString;
}

std::string intToTerString(const Tryte_t& integer) {
	Tryte_t temp = integer;
	std::string terString;
	while (temp != 0) {
		if ((temp + 1) % 3 == 0) {
			terString.insert(terString.begin(), '-');
			temp = (temp + 1) / 3;
		} else if (temp % 3 == 0) {
			terString.insert(terString.begin(), '0');
			temp = temp / 3;
		} else {
			terString.insert(terString.begin(), '+');
			temp = (temp - 1) / 3;
		}
	}
	terString.insert(terString.begin(), TRITS_PER_TRYTE - terString.size(), '0');
	return terString;
}

